const fs = require('fs');
const fse = require('fs-extra');  // 文件拷贝操作，比原生的fs强
const NODE_ENV = process.env.NODE_ENV
const path = require('path')
const { app, ipcMain, shell, dialog } = require('electron')  // shell操作FFmpeg获取缩略图等
const { exec } = require("child_process");  // 进程相关
const FormData = require('form-data');  // 用于构建表单数据
const axios = require('axios');
import { getContentType } from "../renderer/src/utils/Constants";
import { getChatMessage } from "./db/models/ChatMessageModel";
import { getUserSetting, updateSysSetting } from "./db/models/UserSettingModel";
import store from "./store"
import { getAllWindows, getWindow } from "./windowProxy";
const moment = require('moment')  // 时间格式化
moment.locale('zh-cn', {});
const express = require('express')  // 本地服务器，用于提供静态资源服务
const expressServer = express();

/* 获取资源路径，打包前后是不一样的 */
const getResourcesPath = () => {
    let resourcesPath = app.getAppPath();
    if (NODE_ENV !== "development") {
        // 打包后的资源路径在exe同级目录的resources文件夹下
        resourcesPath = path.dirname(app.getPath("exe")) + "/resources"
    }
    return resourcesPath
}

const resourcesPath = getResourcesPath();
const cover_suffix = "_cover.png";
const image_suffix = ".png";
const ffprobePath = path.join(resourcesPath, "/.assets/ffprobe.exe");
const ffmpegPath = path.join(resourcesPath, "/.assets/ffmpeg.exe");

const domain = NODE_ENV !== "development" ? store.getData("prodDomain") : store.getData("devDomain")

/* 执行cmd命令 */
const execCommand = (command) => {
    return new Promise((resolve, reject) => {
        exec(command, (error, stdout, stderr) => {
            console.log("exec command: ", command)
            console.log(`stdout: ${stdout}`)
            if (error) {
                console.error(`exec error: ${error}`)
            }
            resolve(stdout)
        })
    })
}
/* 将消息对应的文件保存到应用的本地目录 */
const saveAndUploadFile = (messageId, filePath, fileType) => {
    return new Promise(async (resolve, reject) => {
        // 获取拷贝到的路径
        const savePath = await getLocalFilePath("chat", false, messageId)
        // console.log(filePath, savePath)
        // 拷贝文件
        fs.copyFileSync(filePath, savePath)
        // 视频文件可能需要重新编码
        if (fileType == 1 || fileType == "VIDEO") {
            // 获取编码格式，需要是h264，不是则重新编码
            let command = `${ffprobePath} -v error -select_streams v:0 -show_entries stream=codec_name "${savePath}"`
            let result = await execCommand(command)
            // 解析出编码格式
            result = result.replaceAll("\r\n", "");
            result = result.replaceAll("\n", "");
            // [STREAM]codec_name=h264[/STREAM]
            result = result.substring(result.indexOf("=") + 1);
            const codec = result.substring(0, result.indexOf("["));
            console.log("codec: ", codec)
            if (codec != "h264") {
                // 重新编码
                command = `${ffmpegPath} -y -i "${savePath}" -c:v libx264 -crf 20 "${savePath}"`
                await execCommand(command)
            }
        }
        // 图片或视频文件要生成缩略图
        let coverPath = null
        if (fileType != 2 && fileType != "OTHER") {
            coverPath = await getLocalFilePath("chat", true, messageId)
            let command = `${ffmpegPath} -i "${savePath}" -y -vframes 1 -vf "scale=min(170\\,iw*min(170/iw\\,170/ih)):min(170\\,ih*min(170/iw\\,170/ih))" "${coverPath}"`
            await execCommand(command)
        }
        // 上传文件
        await uploadFile(messageId, savePath, coverPath)
        resolve();
    })
}

/* 上传文件 */
const uploadFile = async (messageId, savePath, coverPath) => {
    let form = new FormData();
    form.append("messageId", messageId)
    form.append("file", fs.createReadStream(savePath));
    if (coverPath) {
        // 文件信息是没有生成缩略图的
        form.append("cover", fs.createReadStream(coverPath));
    }
    const token = store.getUserData("token")
    await axios.post(`${domain}/api/chat/uploadFile`, form, {
        headers: {
            "Content-Type": "multipart/form-data",
            "token": token
        }
    }).then((res) => {
        console.log("uploadFile response: ", res.data)
    }).catch((err) => {
        console.error("uploadFile error: ", err)
    })
}

/* 根据文件类型获取文件保存的路径 fileId可能是消息id，也可能是用户/群聊的id */
const getLocalFilePath = (partType, isCover, fileId) => {
    return new Promise(async (resolve, reject) => {
        let localFolder = store.getUserData("fileFolder")  // 项目根目录fileStorage + 用户id

        let localPath = null;
        switch (partType) {
            case "avatar":
                // 头像多一层路径
                localFolder = path.join(localFolder, "avatar")
                if (!fs.existsSync(localFolder)) {
                    // 递归创建文件夹
                    console.log("make directory: ", localFolder)
                    fs.mkdirSync(localFolder, { recursive: true })
                }
                localPath = path.join(localFolder, fileId + (isCover ? cover_suffix : image_suffix))
                break;
            case "chat":
                // 获取消息数据，以消息的发送时间构建文件夹等
                const message = await getChatMessage(fileId)
                const month = moment(Number.parseInt(message.sendTime)).format('yyyyMM')
                // 根目录 + 用户id + 年月
                localFolder = path.join(localFolder, month)
                if (!fs.existsSync(localFolder)) {
                    // 递归创建文件夹
                    console.log("make directory: ", localFolder)
                    fs.mkdirSync(localFolder, { recursive: true })
                }
                // 获取文件后缀，重命名文件为 消息id + 文件后缀
                let fileSuffix = message.fileName.substring(message.fileName.lastIndexOf("."))
                localPath = path.join(localFolder, fileId + (isCover ? cover_suffix : fileSuffix))
                break;
            case "temp":
                localFolder = path.join(localFolder, "temp")
                if (!fs.existsSync(localFolder)) {
                    // 递归创建文件夹
                    console.log("make directory: ", localFolder)
                    fs.mkdirSync(localFolder, { recursive: true })
                }
                localPath = path.join(localFolder, fileId)  // 这里的fileId其实是temp+后缀的全名，见saveClipboardFile里的调用
                break;
            case "update":
                localFolder = path.join(localFolder, "app")
                if (!fs.existsSync(localFolder)) {
                    // 递归创建文件夹
                    console.log("make directory: ", localFolder)
                    fs.mkdirSync(localFolder, { recursive: true })
                }
                localPath = path.join(localFolder, fileId)
                break;
            default:
                break;
        }
        resolve(localPath);
    })
}

/* 生成头像文件的缩略图，返回原本的头像流和压缩的缩略图流 */
const createAvatarCover = (filePath) => {
    return new Promise(async (resolve, reject) => {
        // 不能直接用原本的头像路径，更换头像的时候没确定就不修改，所以这是个临时文件，名字加个_temp
        let avatarPath = await getLocalFilePath("avatar", false, store.getUserId() + "_temp")
        // 复制操作会简单压缩一下图片，之后再生成缩略图
        let command = `${ffmpegPath} -i "${filePath}" "${avatarPath}" -y`
        await execCommand(command)
        // 生成缩略图
        let coverPath = await getLocalFilePath("avatar", true, store.getUserId() + "_temp")
        command = `${ffmpegPath} -i "${filePath}" -y -vframes 1 -vf "scale=min(170\\,iw*min(170/iw\\,170/ih)):min(170\\,ih*min(170/iw\\,170/ih))" "${coverPath}"`
        await execCommand(command)
        resolve({
            avatarStream: fs.readFileSync(avatarPath),
            coverStream: fs.readFileSync(coverPath)
        });
    })
}

/* 获取一个本地的小型服务器，用于文件的访问操作 */
let server = null;
const startServer = (serverPort) => {
    server = expressServer.listen(serverPort, () => {
        console.log(`server is running at http://localhost:${serverPort}`)
    })
}

const closeServer = () => {
    if (server) {
        server.close(() => {
            console.log("server closed")
        })
    }
}

// 定义接口
expressServer.get("/file", async (req, res) => {
    // 获取参数
    let { partType, fileType, fileId, isCover, forceGet } = req.query
    console.log("file request: ", req.query)
    if (!partType || !fileId) {
        res.status(403).send("参数错误")
        return;
    }
    isCover = isCover == undefined ? false : isCover == "true"  // 转换为布尔值
    forceGet = forceGet == undefined ? false : forceGet == "true"  // 转换为布尔值
    const localPath = await getLocalFilePath(partType, isCover, fileId)
    console.log("localPath: ", localPath)
    if (!fs.existsSync(localPath) || forceGet) {
        console.log("file not exist or 'forceGet' is true, need download: ", localPath)
        // 文件不存在，需要下载（比如说换头像了，需要重新从服务端拉取）
        if (partType == "avatar" && forceGet) {
            // 头像获取缩略图
            let coverPath = await getLocalFilePath(partType, true, fileId);
            await downloadFile(fileId, true, coverPath, partType)
        }
        // 正常按要求下载
        await downloadFile(fileId, isCover, localPath, partType);
        // 让渲染进程把forceReload改回false，使用更新后的本地文件
        const allWindows = getAllWindows()
        for (let key in allWindows) {
            allWindows[key].webContents.send("setForceReload", { id: fileId, value: false })
        }
    }
    // 不同文件的content-type不同
    let contentType = getContentType(localPath)
    res.setHeader("Content-Type", contentType)
    // 允许跨域访问
    res.setHeader("Access-Control-Allow-Origin", "*")
    if (isCover || (fileType != "1" && fileType != "VIDEO")) {
        // 非视频类的文件直接返回文件流
        fs.createReadStream(localPath).pipe(res)
        return
    }
    // 视频需要达到点击进度条的哪里就从哪里开始播放
    let stat = fs.statSync(localPath)
    let fileSize = stat.size
    let range = req.headers.range
    if (range) {
        // 获取请求里的视频的开始和结束位置
        let parts = range.replace(/bytes=/, "").split("-")
        let start = parseInt(parts[0], 10)
        let end = parts[1] ? parseInt(parts[1], 10) : Infinity
        end = end > fileSize - 1 ? fileSize - 1 : end
        let chunkSize = (end - start) + 1
        let stream = fs.createReadStream(localPath, { start, end })
        let head = {
            "Content-Range": `bytes ${start}-${end}/${fileSize}`,
            "Accept-Ranges": "bytes",
            "Content-Length": chunkSize,
            "Content-Type": 'video/mp4'
        }
        res.writeHead(206, head)
        stream.pipe(res)
    } else {
        // 视频较小可能没有range，直接返回
        let head = {
            "Content-Length": fileSize,
            "Content-Type": 'video/mp4'
        }
        res.writeHead(200, head)
        fs.createReadStream(localPath).pipe(res)
    }
    return
})

/* 从服务器下载文件，下载到savePath */
const downloadFile = (fileId, isCover, savePath, partType) => {
    return new Promise(async (resolve, reject) => {
        const token = store.getUserData("token")
        let url = `${domain}/api/chat/downloadFile?fileId=${fileId}`
        if (isCover) {
            url += "&isCover=true"
        } else {
            url += "&isCover=false"
        }
        axios({
            method: "get",
            url,
            responseType: "stream",
            headers: {
                "token": token
            }
        }).then((res) => {
            const stream = fs.createWriteStream(savePath)
            if (res.headers["content-type"] == "application/json") {
                // 返回的是json数据，说明文件不存在
                console.log("no such file in server, replace default image to path: ", savePath)
                if (partType == "avatar") {
                    // 头像不存在，返回默认头像
                    if (fileId[0] == 'U') {
                        fs.createReadStream(resourcesPath + "/.assets/user.png").pipe(stream)
                    } else {
                        fs.createReadStream(resourcesPath + "/.assets/group.png").pipe(stream)
                    }
                } else {
                    // 其他文件不存在，返回404图片
                    fs.createReadStream(resourcesPath + "/.assets/404.png").pipe(stream)
                }
                stream.on("finish", () => {
                    stream.close()
                    console.log("load default image to path success: ", savePath)
                    resolve()
                })
            } else {
                // 正常返回的是文件流
                res.data.pipe(stream)
                stream.on("finish", () => {
                    stream.close()
                    console.log("download file success: ", savePath)
                    resolve()
                })
            }
        })
    })
}

/* 在查看图片/视频/其他文件时保存文件到本地 */
const saveAs = async ({ fileId, partType }) => {
    let fileName = ""
    switch (partType) {
        case "avatar":
            fileName = fileId + image_suffix
            break;
        case "chat":
            const message = await getChatMessage(fileId)
            fileName = message.fileName
            break;
    }
    const localPath = await getLocalFilePath(partType, false, fileId)
    // 使用electron的dialog模块保存文件
    const options = {
        title: "保存文件",
        defaultPath: fileName
    }
    let result = await dialog.showSaveDialog(options)
    if (result.canceled || result.filePath == null || result.filePath == "") return;
    fs.copyFileSync(localPath, result.filePath)
    console.log("save file success: ", result.filePath)
}

/* 保存剪贴板的文件为临时文件 */
const saveClipboardFile = async ({ name, byteArray }) => {
    const fileSuffix = name.substring(name.lastIndexOf("."))
    const localPath = await getLocalFilePath("temp", false, "temp" + fileSuffix)
    const buffer = Buffer.from(byteArray)
    fs.writeFileSync(localPath, buffer)
    return {
        size: buffer.byteLength,
        name: name,
        path: localPath
    }
}

/* 打开本地的保存用户数据的文件夹目录 */
const openFileFolder = async () => {
    let userSetting = await getUserSetting()
    const sysSetting = JSON.parse(userSetting.sysSetting)
    const fileFolder = sysSetting.fileFolder
    if (!fs.existsSync(fileFolder)) {
        fs.mkdirSync(fileFolder, { recursive: true })
    }
    shell.openPath('file://' + fileFolder)
}

/* 修改保存用户数据的文件夹目录 */
const changeFileFolder = async () => {
    // 打开原本的路径
    let userSetting = await getUserSetting()
    const sysSetting = JSON.parse(userSetting.sysSetting)
    const fileFolder = sysSetting.fileFolder

    const options = {
        title: "选择文件保存目录",
        properties: ["openDirectory"],
        defaultPath: fileFolder
    }
    let result = await dialog.showOpenDialog(options)
    if (result.canceled || !result.filePaths[0]) return;
    const newFileFolder = result.filePaths[0]

    if (newFileFolder == fileFolder) return;
    // 选择了新路径，需要把文件拷贝过去
    // 通知渲染进程显示正在拷贝文件的动画
    getWindow('main').webContents.send("copying")
    await fse.copy(fileFolder, newFileFolder)
    // 更新数据库内容
    sysSetting.fileFolder = newFileFolder
    await updateSysSetting(sysSetting)
    // 更新store中的内容
    store.setUserData("fileFolder", newFileFolder)
}

/* 下载更新的应用文件 */
const downloadUpdate = async ({ version, fileName }) => {
    let url = `${domain}/api/update/download?version=${version}`
    const token = store.getUserData("token")
    const config = {
        responseType: "stream",
        headers: {
            "token": token,
        },
        /* 下载进度回调 */
        onDownloadProgress: progressEvent => {
            const loaded = progressEvent.loaded
            getWindow('main').webContents.send("updateDownloadProgress", loaded);
        }
    }
    // 下载文件
    const response = await axios.get(url, config)
    const localFilePath = await getLocalFilePath("update", false, fileName)
    const stream = fs.createWriteStream(localFilePath)
    response.data.pipe(stream)
    stream.on("finish", async () => {
        stream.close()
        // 安装文件
        const command = `${localFilePath}`
        await execCommand(command)
    })
}

export {
    saveAndUploadFile,
    startServer,
    closeServer,
    createAvatarCover,
    saveAs,
    saveClipboardFile,
    openFileFolder,
    changeFileFolder,
    downloadUpdate
}